<?php
// $Id: submenutree.module,v 1.4.2.3 2009/05/11 09:47:16 bengtan Exp $
// Modeline for drupal
// vim: set expandtab tabstop=2 shiftwidth=2 autoindent smartindent filetype=php:

define('SUBMENUTREE_DISPLAY_MENU', 0);
define('SUBMENUTREE_DISPLAY_TITLES', 1);
define('SUBMENUTREE_DISPLAY_TEASERS', 2);
define('SUBMENUTREE_DISPLAY_TEASERS_LINKS', 3);
define('SUBMENUTREE_DISPLAY_FULLTEXT', 4);
define('SUBMENUTREE_DISPLAY_FULLTEXT_LINKS', 5);

define('SUBMENUTREE_DISPLAY_BLOCK_MASK', 15);
define('SUBMENUTREE_DISPLAY_BLOCK_MENU', 16);
define('SUBMENUTREE_DISPLAY_BLOCK_TITLES', 17);
define('SUBMENUTREE_DISPLAY_BLOCK_TEASERS', 18);
define('SUBMENUTREE_DISPLAY_BLOCK_TEASERS_LINKS', 19);
define('SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT', 20);
define('SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT_LINKS', 21);

define('SUBMENUTREE_BLOCK_SUBMENU', 0);
define('SUBMENUTREE_BLOCK_SIBLINGMENU', 1);


/**
 * Implementation of hook_help().
 */
function submenutree_help($section) {
  switch ($section) {
    case 'admin/help#submenutree':
      return t("For nodes which are displayed as menu items, this module adds a listing of any submenu items below it into the content or into a block. It can also add a listing of sibling menu items.");
  }
}


/**
 * Implementation of hook_form_alter().
 */
function submenutree_form_alter(&$form, $form_state, $form_id) { 
  if (strpos($form_id, 'node_form') > 0) {
    $node = $form['#node'];
    // Inject some sane defaults
    if (empty($node->submenutree_enable)) {
      $node->submenutree_enable = 0;
      $node->submenutree_display = SUBMENUTREE_DISPLAY_MENU;
      $node->submenutree_weight = 1;
    }
    if (empty($node->siblingmenutree_enable)) {
      $node->siblingmenutree_enable = 0;
      $node->siblingmenutree_display = SUBMENUTREE_DISPLAY_MENU;
      $node->siblingmenutree_weight = 1;
    }

    $form['menu']['submenutree'] = array(
      '#type' => 'fieldset',
      '#title' => t('Submenu Tree Settings'),
      '#collapsible' => true,
      '#collapsed' => !$node->submenutree_enable,

      'submenutree_enable' => array(
        '#type' => 'checkbox',
        '#title' => t('Enable submenu trees for this node'),
        '#default_value' => $node->submenutree_enable,
      ),

      'submenutree_title' => array(
        '#type' => 'textfield',
        '#title' => t('Title'),
        '#default_value' => $node->submenutree_title,
        '#description' => t('The title of the submenu tree content or block. If you leave this blank, the submenu tree content will have no title, or the submenu tree block will use the node title.'),
      ),

      'submenutree_display' => array(
        '#type' => 'select',
        '#title' => t('Display submenu trees as'),
        '#options' => array(
          'content' => array(
            SUBMENUTREE_DISPLAY_MENU => t('Menu'),
            SUBMENUTREE_DISPLAY_TITLES => t('Titles only'),
            SUBMENUTREE_DISPLAY_TEASERS => t('Teasers'),
            SUBMENUTREE_DISPLAY_TEASERS_LINKS => t('Teasers with links'),
            SUBMENUTREE_DISPLAY_FULLTEXT => t('Full text'),
            SUBMENUTREE_DISPLAY_FULLTEXT_LINKS => t('Full text with links'),
            ),
          'block' => array(
            SUBMENUTREE_DISPLAY_BLOCK_MENU => t('Menu'),
            SUBMENUTREE_DISPLAY_BLOCK_TITLES => t('Titles only'),
            SUBMENUTREE_DISPLAY_BLOCK_TEASERS => t('Teasers'),
            SUBMENUTREE_DISPLAY_BLOCK_TEASERS_LINKS => t('Teasers with links'),
            SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT => t('Full text'),
            SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT_LINKS => t('Full text with links'),
            ),
        ),
        '#default_value' => $node->submenutree_display,
        '#description' => t('Select where and how the submenu tree should be displayed. If selecting a block display, the block also needs to be made visible.'),
      ),

      'submenutree_weight' => array(
        '#type' => 'weight',
        '#title' => t('Weight'),
        '#default_value' => $node->submenutree_weight,
        '#description' => t('The weight of the submenu tree listing. This only applies when displaying as content and affects where the submenu tree appears in the content.'),
      ),
    );

    $form['menu']['siblingmenutree'] = array(
      '#type' => 'fieldset',
      '#title' => t('Siblingmenu Tree Settings'),
      '#collapsible' => true,
      '#collapsed' => !$node->siblingmenutree_enable,

      'siblingmenutree_enable' => array(
        '#type' => 'checkbox',
        '#title' => t('Enable siblingmenu trees for this node'),
        '#default_value' => $node->siblingmenutree_enable,
      ),

      'siblingmenutree_title' => array(
        '#type' => 'textfield',
        '#title' => t('Title'),
        '#default_value' => $node->siblingmenutree_title,
        '#description' => t('The title of the siblingmenu tree content or block. If you leave this blank, the siblingmenu tree content will have no title, or the siblingmenu tree block will use the node title.'),
      ),

      'siblingmenutree_display' => array(
        '#type' => 'select',
        '#title' => t('Display siblingmenu trees as'),
        '#options' => array(
          'content' => array(
            SUBMENUTREE_DISPLAY_MENU => t('Menu'),
            SUBMENUTREE_DISPLAY_TITLES => t('Titles only'),
            SUBMENUTREE_DISPLAY_TEASERS => t('Teasers'),
            SUBMENUTREE_DISPLAY_TEASERS_LINKS => t('Teasers with links'),
            SUBMENUTREE_DISPLAY_FULLTEXT => t('Full text'),
            SUBMENUTREE_DISPLAY_FULLTEXT_LINKS => t('Full text with links'),
            ),
          'block' => array(
            SUBMENUTREE_DISPLAY_BLOCK_MENU => t('Menu'),
            SUBMENUTREE_DISPLAY_BLOCK_TITLES => t('Titles only'),
            SUBMENUTREE_DISPLAY_BLOCK_TEASERS => t('Teasers'),
            SUBMENUTREE_DISPLAY_BLOCK_TEASERS_LINKS => t('Teasers with links'),
            SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT => t('Full text'),
            SUBMENUTREE_DISPLAY_BLOCK_FULLTEXT_LINKS => t('Full text with links'),
            ),
        ),
        '#default_value' => $node->siblingmenutree_display,
        '#description' => t('Select where and how the siblingmenu tree should be displayed. If selecting a block display, the block also needs to be made visible.'),
      ),

      'siblingmenutree_weight' => array(
        '#type' => 'weight',
        '#title' => t('Weight'),
        '#default_value' => $node->siblingmenutree_weight,
        '#description' => t('The weight of the siblingmenu tree listing. This only applies when displaying as content and affects where the siblingmenu tree appears in the content.'),
      ),
    );
  }
}


/**
 * Implementation of hook_nodeapi().
 */
function submenutree_nodeapi(&$node, $op, $teaser, $page) { 
  switch ($op) {
    case 'delete':
      db_query('DELETE FROM {node_submenutree} WHERE nid = %d', $node->nid);
      break;

    case 'load':
      $additions = db_fetch_array(db_query('SELECT submenutree_enable, submenutree_title, submenutree_display, submenutree_weight, siblingmenutree_enable, siblingmenutree_title, siblingmenutree_display, siblingmenutree_weight FROM {node_submenutree} WHERE nid = %d', $node->nid));
      if ($additions)
        return $additions;
      break;

    case 'update':
      if (!$node->submenutree_enable && !$node->siblingmenutree_enable) {
        db_query('DELETE FROM {node_submenutree} WHERE nid = %d', $node->nid);
      }
      // deliberate fullthrough

    case 'insert':
      if ($node->submenutree_enable || $node->siblingmenutree_enable) {
        // SQL INSERT or UPDATE depending on whether a row already exists
        $num_rows = db_result(db_query('SELECT COUNT(*) FROM {node_submenutree} WHERE nid = %d', $node->nid));
        if ($num_rows > 0)
          db_query("UPDATE {node_submenutree} SET submenutree_enable = %d, submenutree_title = '%s', submenutree_display = %d, submenutree_weight = %d, siblingmenutree_enable = %d, siblingmenutree_title = '%s', siblingmenutree_display = %d, siblingmenutree_weight = %d WHERE nid = %d", $node->submenutree_enable, $node->submenutree_title, $node->submenutree_display, $node->submenutree_weight, $node->siblingmenutree_enable, $node->siblingmenutree_title, $node->siblingmenutree_display, $node->siblingmenutree_weight, $node->nid);
        else 
          db_query("INSERT INTO {node_submenutree} (nid, submenutree_enable, submenutree_title, submenutree_display, submenutree_weight, siblingmenutree_enable, siblingmenutree_title, siblingmenutree_display, siblingmenutree_weight) VALUES (%d, %d, '%s', %d, %d, %d, '%s', %d, %d)", $node->nid, $node->submenutree_enable, $node->submenutree_title, $node->submenutree_display, $node->submenutree_weight, $node->siblingmenutree_enable, $node->siblingmenutree_title, $node->siblingmenutree_display, $node->siblingmenutree_weight);
      }
      break;

    case 'presave':
      // copy fields from $node->menu['submenutree']['submenutree_*'] to ['submenutree_*'] if necessary
      // Shouldn't drupal flatten these form fields already? Apparently not
      if (!empty($node->menu['submenutree'])) {
        foreach ($node->menu['submenutree'] as $k => $v)
          $node->$k = $v;
      }
      if (!empty($node->menu['siblingmenutree'])) {
        foreach ($node->menu['siblingmenutree'] as $k => $v)
          $node->$k = $v;
      }
      break;

    case 'view':
      // Calling a helper function because it's too big to live in the switch
      submenutree_nodeapi_view($node, $op, $teaser, $page);
      break;
  }
}


function submenutree_nodeapi_view(&$node, $op, $teaser, $page) {
  if ($teaser == false && $page == true && ($node->submenutree_enable || $node->siblingmenutree_enable)) {
    $menu_name = variable_get('menu_default_node_menu', 'primary-links');
  
    // Give priority to the default menu
    $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND menu_name = '%s' AND module = 'menu' ORDER BY mlid ASC", $node->nid, $menu_name, 0, 1));
    // Check all menus if a link does not exist in the default menu.
    if (!$mlid) {
      $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu' ORDER BY mlid ASC", $node->nid, 0, 1));
    }

    if ($mlid) {
      $item = menu_link_load($mlid);
      $tree = menu_tree_page_data($item['menu_name']);

      // Traverse down the tree to find our node, following in_active_trial
      // This code stanza is loosely inspired by menu_set_active_trail()
      list($key, $curr) = each($tree);
      while ($curr) {
        if ($curr['link']['href'] == $item['href']) {
          $my_tree = $curr['below'];
          $parent_tree = $tree;
          $curr = FALSE;
        }
        else {
          // Move to the child link if it's in the active trail.
          if ($curr['below'] && $curr['link']['in_active_trail']) {
            $tree = $curr['below'];
          }
          list($key, $curr) = each($tree);
        }
      }

      // Sanity check that we did find something
      if ($my_tree && $node->submenutree_enable) {
        _submenutree_menutree_view($node, 'submenutree', $my_tree);          
      }
      if ($parent_tree && $node->siblingmenutree_enable) {
        _submenutree_menutree_view($node, 'siblingmenutree', $parent_tree);          
      }
    }
  }
}


/**
 * Implementation of hook_block().
 */
function submenutree_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $blocks[SUBMENUTREE_BLOCK_SUBMENU]['info'] = t('Submenu Tree Display');
    $blocks[SUBMENUTREE_BLOCK_SUBMENU]['cache'] = BLOCK_CACHE_PER_PAGE;
    $blocks[SUBMENUTREE_BLOCK_SIBLINGMENU]['info'] = t('Siblingmenu Tree Display');
    $blocks[SUBMENUTREE_BLOCK_SIBLINGMENU]['cache'] = BLOCK_CACHE_PER_PAGE;

    $blocks[2]['info'] = 'Extended Primary Links';
    $blocks[2]['cache'] = BLOCK_CACHE_PER_PAGE;
    return $blocks;
  } 
  else if ($op == 'view') {
    switch ($delta) {
      case SUBMENUTREE_BLOCK_SUBMENU:
      case SUBMENUTREE_BLOCK_SIBLINGMENU:
        return _submenutree_get_block_content($delta);
        break;

      case 2:
        return _submenutree_block_extended_primary_links();
    }
  }
}


/**
 * Implementation of hook_theme().
 */
function submenutree_theme() {
  return array(
    'submenu_tree_menu' => array(
    ),
    'submenu_tree_titles' => array(
    ),
    'submenu_tree_teasers' => array(
    ),
    'submenu_tree_fulltext' => array(
    ),
  );
}


/**
 * View the menu tree, either by poking into $node->content, or via the block functions
 * 
 * @param $node
 *   The node being operated upon. This is also used for configuration information. 
 * @param $type
 *   The type of menu to produce, either "submenutree" or "siblingmenutree"
 * @param $tree
 *   A fragment of a menu tree to be viewed
 */

function _submenutree_menutree_view(&$node, $type, $tree) {
  // grab config from $node, depending on $type
  $config_item = $type . '_title';
  $title = $node->$config_item;
  $config_item = $type . '_display';
  $display = intval($node->$config_item) & SUBMENUTREE_DISPLAY_BLOCK_MASK;
  $display_in_block = intval($node->$config_item) & ~SUBMENUTREE_DISPLAY_BLOCK_MASK;
  $config_item = $type . '_weight';
  $weight = $node->$config_item;

  // tweak $title
  if ($display_in_block) {
    $block_title = $title;
    if ($block_title == '')
      $block_title = $node->title;
    // wipe out $title so it doesn't get passed into the theme functions
    $title = null;
  }
  else {
    if ($title == '')
      $title = null;
  }

  $output = '';
  if ($display == SUBMENUTREE_DISPLAY_MENU) {
    $output = theme('submenu_tree_menu', $tree, $title);
  }
  else {
    $items = array();
    foreach ($tree as $k => $v) {
      // use page_callback == node_page_view or delegator_node_view to detect nodes
      if ($v['link']['hidden'] == false && in_array($v['link']['page_callback'], array('node_page_view', 'delegator_node_view'))) {
        $nid = substr($v['link']['href'], 5);
        $child = node_load(array('nid' => $nid));
        $items[] = array('node' => $child, 'weight' => $v['link']['weight'], 'title' => check_plain($v['link']['title']));
      }
    }
    _submenutree_sort_items($items);

    // Now render our links or our nodes
    $links = false;
    switch ($display) {
      case SUBMENUTREE_DISPLAY_TITLES:
        $output = theme('submenu_tree_titles', $items, $title);
        break;

      case SUBMENUTREE_DISPLAY_TEASERS_LINKS:
        $links = true;
      case SUBMENUTREE_DISPLAY_TEASERS:
        $output = theme('submenu_tree_teasers', $items, $title, $links);
        break;

      case SUBMENUTREE_DISPLAY_FULLTEXT_LINKS:
        $links = true;
      case SUBMENUTREE_DISPLAY_FULLTEXT:
        $output = theme('submenu_tree_fulltext', $items, $title, $links);
        break;
    }
  }

  if ($output) {
    if ($display_in_block == 0) {
      $node->content[$type] = array(
        '#value' => $output,
        '#weight' => $weight,
      );
    }
    else {
      $blocks_map = array(
        'submenutree' => SUBMENUTREE_BLOCK_SUBMENU, 
        'siblingmenutree' => SUBMENUTREE_BLOCK_SIBLINGMENU, 
      );
      _submenutree_set_block_content($blocks_map[$type], array('subject' => $block_title, 'content' => $output));
    }
  }
}


/**
 * $block should be an array like those used in hook_block
 *    ie array('subject' => 'title', 'content' => 'some content');
 */
function _submenutree_set_block_content($delta, $block = null) {
  static $stored_content = array(
    SUBMENUTREE_BLOCK_SUBMENU => array(),
    SUBMENUTREE_BLOCK_SIBLINGMENU => array(),
  );

  if (isset($block) && is_array($block)) {
    $stored_content[$delta] = $block;
  }
  return $stored_content[$delta];
}


function _submenutree_get_block_content($delta) {
  return _submenutree_set_block_content($delta);
}


/**
 * Return block of extended primary links
 * 
 */

function _submenutree_block_extended_primary_links() {
  // Choose the appropriate menu root
  $primary_name = variable_get('menu_primary_links_source', 'primary-links');
  $secondary_name = variable_get('menu_secondary_links_source', 'secondary-links');
  if ($primary_name) {
    $menu_name = $primary_name;
    if ($primary_name != $secondary_name)            
      $level = 1;    // Select the second level of the primary menu
    else
      $level = 2;    // Select the third level of the primary menu
  }
  else if ($secondary_name) {
    $menu_name = $secondary_name;
    $level = 1;    // Select the second level of the secondary menu
  }
  else {
    break;
  }

  // Loosely derived from menu_navigation_links()        
  $tree = menu_tree_page_data($menu_name);
  // Go down the active trail until the right level is reached.
  while ($level-- > 0 && $tree) {
    // Loop through the current level's items until we find one that is in trail.
    while ($item = array_shift($tree)) {
      if ($item['link']['in_active_trail']) {
        $parent = $item;
        // If the item is in the active trail, we continue in the subtree.
        $tree = empty($item['below']) ? array() : $item['below'];
        break;
      }
    }
  }

  $title = $parent['link']['title'];
  $output = menu_tree_output($tree);

  if (!empty($output)) {
    $block = array('subject' => $title, 'content' => $output);
    return $block;
  }
}


/**
 * Sort an array of items.
 * 
 * @param $items
 *   The array of items to be sorted.
 */
function _submenutree_sort_items(&$items) {
  usort($items, '_submenutree_sort_items_compare');
}


/**
 * Compare two items by weight then title.
 *
 * @param a
 *   The first item to compare.
 * @param b
 *   The second item to compare.
 * @return
 *   An integer less than, equal to, or greater than zero if $a is considered
 *   to be respectively less than, equal to, or greater than $b.
 */
function _submenutree_sort_items_compare($a, $b) {
  $ret = $a['weight'] - $b['weight'];
  if ($ret == 0) {
    $ret = strcasecmp($a['title'], $b['title']);
  }
  return $ret;
}


function theme_submenu_tree_menu($tree, $title = null) {
  $output = '';
  if (isset($title))
    $output .= '<h3>'. $title .'</h3>';
  $output .= menu_tree_output($tree);
  return $output;
}


function theme_submenu_tree_titles($items, $title = null) {
  $list = array();
  foreach ($items as $item) {
    $list[] = l($item['node']->title, 'node/' . $item['node']->nid);
  }
  return theme('item_list', $list, $title);
}


function theme_submenu_tree_teasers($items, $title = null, $links) {
  $output = '';
  if (isset($title))
    $output .= '<h3>'. $title .'</h3>';
  foreach ($items as $item) {
    $output .= node_view($item['node'], true, false, $links);
  }
  return $output;
}


function theme_submenu_tree_fulltext($items, $title = null, $links) {
  $output = '';
  if (isset($title))
    $output .= '<h3>'. $title .'</h3>';
  foreach ($items as $item) {
    $output .= node_view($item['node'], false, false, $links);
  }
  return $output;
}


